package morenoapp.util;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.Timer;
import morenoapp.MorenoApp;
import org.apache.commons.io.FileUtils;
import org.apache.poi.hssf.util.HSSFColor;

/**
 *
 * @author root
 */
public class MorenoUtil {
    //private static final Logger exceptionLogger = Logger.getLogger("morena.exceptions");

    private static Boolean debug = null;
    public static boolean isDebug() {
        if (debug == null) {
            debug = System.getProperty("morena.debug") != null;
        }
        return debug.booleanValue();
    }

    public static JComponent prefWidth(JComponent component, int prefferedWidth){
        Dimension dim = component.getPreferredSize();
        component.setPreferredSize(new Dimension(prefferedWidth, dim.height));
        return component;
    }

    public static JPanel createBoxLayoutPanel(int axis){
        JPanel panel = new JPanel();
        BoxLayout layout = new BoxLayout(panel, axis);
        panel.setLayout(layout);
        return panel;
    }

    public static String limitString(String _text, int _limit) {
        if (_text == null) {
            return "" + null; //NOI18N
        } else {
            int realLimit = (_limit < 3) ? 3 : _limit;
            if (_text.length() <= realLimit) {
                return _text;
            } else {
                return _text.substring(0, realLimit) + "..."; //NOI18N
            }
        }
    }

    public static String colorToHtml(Color _color) {
        return String.format("#%06x", _color.getRGB() & 0xffffff); //NOI18N
    }

    public static String wrapTextWithColor(Color _color, String _text) {
        return "<font color='" + colorToHtml(_color) + "'>" + _text + "</font>"; //NOI18N
    }

    public static void highlightPanel(final JPanel _panel) {
        final Timer timer = new Timer(50, null);
        timer.addActionListener(new ActionListener() {
            private static final int TOTAL = 3;
            private Color naturalColor = _panel.getBackground();
            private int count = TOTAL * 2;
            public void actionPerformed(ActionEvent e) {
                count--;
                if (count == 0) {
                    timer.stop();
                    _panel.setBackground(naturalColor);
                } else {
                    Color currentColor = gradient(naturalColor, Color.YELLOW,
                            (1.0f - ((float) Math.abs(TOTAL - count)) / TOTAL));
                    _panel.setBackground(currentColor);
                }
                _panel.repaint();
            }

        });
        timer.start();
    }

    public static HSSFColor getClosestHSSFColor(Color c) {
        double measure = Double.MAX_VALUE;
        HSSFColor result = null;
        for (HSSFColor s : (Collection<HSSFColor>) HSSFColor.getIndexHash().values()) {
            double sMeasure = diffColors(c, s);
            if (sMeasure < measure) {
                measure = sMeasure;
                result = s;
            }
        }
        return result;
    }

    private static double diffColors(Color c, HSSFColor s) {
        int dr = c.getRed() - s.getTriplet()[0];
        int dg = c.getGreen() - s.getTriplet()[1];
        int db = c.getBlue() - s.getTriplet()[2];
        return Math.sqrt(dr * dr + dg * dg + db * db);

    }

    public static Color gradient(Color start, Color end, float proportion) {
        float redDiff = (end.getRed() - start.getRed()) * proportion;
        float greenDiff = (end.getGreen() - start.getGreen()) * proportion;
        float blueDiff = (end.getBlue() - start.getBlue()) * proportion;

        return new Color(
                (int) (start.getRed() + redDiff) % 256,
                (int) (start.getGreen() + greenDiff) % 256,
                (int) (start.getBlue() + blueDiff) % 256);
    }

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd+HH-mm-ss");  //NOI18N
    private static final SimpleDateFormat DATE_DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy"); //NOI18N
    public static String dateToString(Date date) {
        if (date == null) {
            return null;
        } else {
            return DATE_FORMAT.format(date);
        }
    }

    public static Date stringToDate(String str) {
        if (str == null) {
            return null;
        } else {
            try {
                return DATE_FORMAT.parse(str);
            } catch (ParseException ex) {
                return null;
            }
        }
    }

    public static String dateToStringDate(Date date) {
        if (date == null) {
            return null;
        } else {
            return DATE_DATE_FORMAT.format(date);
        }

    }

    public static Date stringDateToDate(String str) {
        if (str == null) {
            return null;
        } else {
            try {
                return DATE_DATE_FORMAT.parse(str);
            } catch (ParseException ex) {
                return null;
            }
        }
    }

    public static String getDateDateFormat() {
        return DATE_DATE_FORMAT.toPattern();
    }

    public static String colorToString(Color color) {
        if (color == null) {
            return null;
        } else {
            return String.format("%06x", color.getRGB() & 0xffffff); //NOI18N
        }
    }

    public static String escapeForHtml(String _text){
        return _text
                .replace("&","&amp;")
                .replace("<","&lt;")
                .replace("<","&gt;")
                .replace("\"","&quot;");
    }

    public static void logKeyToFile(File file, String key) {
        try {
            List<String> lines;
            if (file.exists()) {
                lines = FileUtils.readLines(file, "utf-8");
            } else {
                lines = new ArrayList<String>();
            }
            if (lines.contains(key) == false) {
                lines.add(key);
                Collections.sort(lines);
                FileUtils.writeLines(file, "utf-8", lines, "\n");
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

    public static Color stringToColor(String str) {
        if (str == null) {
            return null;
        } else {
            try {
                return new Color(Integer.parseInt(str, 16));
            } catch (NumberFormatException ex) {
                return null;
            }
        }
    }

    public static String toStirng(Throwable th) {
        if (th == null) {
            return "" + th;
        } else {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw, true);
            th.printStackTrace(pw);
            pw.flush();
            sw.flush();
            return sw.toString();
        }
    }


    public static List<Method> filterAndSortMethodsByOrder(Method[] methods){
        List<Method> result = new ArrayList<Method>();
        for(Method m:methods){
            if (m.getAnnotation(Order.class)!=null){
                result.add(m);
            }
        }
        
        Collections.sort(result, new Comparator<Method>() {
            public int compare(Method o1, Method o2) {
                return -(nvl(o2.getAnnotation(Order.class)) - nvl(o1.getAnnotation(Order.class)));
            }

            int nvl(Order order) {
                if (order == null) {
                    return 0;
                } else {
                    return order.value();
                }
            }

        });
        return result;
    }
    public static void drawStringAtComponentCenter(Graphics g, Component c, Color color, Float fontSize, String text) {
        g.setColor(color);
        Font fontBackup = null;

        int componentWidth = c.getWidth();
        if (fontSize!=null){
            fontBackup = g.getFont();
            if (fontSize>0){
                g.setFont(fontBackup.deriveFont(fontSize));
            } else {
                float stringWidth = g.getFontMetrics().stringWidth(text);
                if (stringWidth>0 && componentWidth>0){
                    float fontScale = fontBackup.getSize()*(componentWidth/ stringWidth);
                    fontScale = (float) Math.floor(fontScale-1.0f);
                    g.setFont(fontBackup.deriveFont(fontScale));
                }
            }
        }
        int stringWidth = g.getFontMetrics().stringWidth(text);
        g.drawString(text, (componentWidth - stringWidth) / 2, c.getHeight() / 2);
        if (fontBackup!=null){
            g.setFont(fontBackup);
        }
    }

    public static void renderComponents(Component c,
            final Class<DocumentedComponent> marker,
            final File file) throws IOException {
        new Object() {
            Set<Component> rendered = new HashSet<Component>();
            Map<String, Integer> countersMap = new HashMap<String, Integer>() {
                @Override
                public Integer get(Object key) {
                    if (super.containsKey((String)key) == false) {
                        put((String) key, Integer.valueOf(0));
                    }
                    Integer result = super.get((String)key);
                    put((String) key, Integer.valueOf(result.intValue() + 1));
                    return result;
                }
            };
            void render(Component c) throws IOException {
                //check component instance exists and not yet rendered
                if (c == null || rendered.contains(c) == true) {
                    return;
                }

                //check component size
                Dimension componentSize = c.getSize();
                if (componentSize.getWidth() == 0 || componentSize.getHeight() == 0) {
                    return;
                }

                //render component to image
                if (marker == null || (marker != null && c.getClass().getAnnotation(marker) != null)) {
                    //prepage image for drawing
                    BufferedImage img = new BufferedImage(componentSize.width,
                            componentSize.height,
                            BufferedImage.TYPE_INT_RGB);

                    //Opaque trick
                    boolean opaque = false;
                    if (c instanceof JComponent) {
                        opaque = c.isOpaque();
                        ((JComponent) c).setOpaque(true);
                    }

                    Graphics2D graphics = img.createGraphics();
                    graphics.fillRect(0, 0, img.getWidth(), img.getHeight());
                    c.paint(graphics);
                    graphics.dispose();

                    //Restore Opaque value
                    if (c instanceof JComponent) {
                        ((JComponent) c).setOpaque(opaque);
                    }

                    //get component name
                    String name = findNameForComponent(c);

                    //save image
                    saveImage(img, new File(file, name + "-" + countersMap.get(name) + ".png"));

                    //free memory
                    img = null;
                }

                //mark component as rendred anyway
                rendered.add(c);

                //step down by components tree
                if (c instanceof Container) {
                    Container con = (Container) c;
                    for (int i = 0; i < con.getComponentCount(); i++) {
                        Component ci = con.getComponent(i);
                        render(ci);
                    }
                }
                if (c instanceof JTabbedPane) {
                    JTabbedPane tab = (JTabbedPane) c;
                    for (int i = 0; i < tab.getTabCount(); i++) {
                        Component ci = tab.getTabComponentAt(i);
                        render(ci);
                    }
                }
                if (c instanceof JMenu) {
                    JMenu menu = (JMenu) c;
                    for (int i = 0; i < menu.getMenuComponentCount(); i++) {
                        render(menu.getItem(i));
                    }
                }
            }

            String findNameForComponent(Component c) {
                if (marker != null) {
                    String name = c.getClass().getSimpleName();
                    return name + countersMap.get(name);
                } else {
                    String name = "";
                    Component current = c;
                    while (current != null) {
                        name = "/" + String.valueOf(current.getName()) + name;
                        current = current.getParent();
                    }
                    name = name.substring(1);
                    return name;
                }
            }

            private void saveImage(BufferedImage img, File file) throws IOException {
                if (file.getParentFile().exists() == false) {
                    file.getParentFile().mkdirs();
                }
                ImageIO.write(img, "png", file); //NOI18N
            }

        }.render(c);
    }

    public static JComponent findComponentByName(JComponent parent, String name) {
        for (Component component : parent.getComponents()) {
            if (component instanceof JComponent) {
                JComponent c = (JComponent) component;
                if (name.equals(c.getName())) {
                    return c;
                } else {
                    JComponent found = findComponentByName(c, name);
                    if (found != null) {
                        return found;
                    }
                }
            }
        }
        return null;
    }

    public static void highlightComponent(final JComponent component) {
        final Timer timer = new javax.swing.Timer(30, null);
        timer.addActionListener(new ActionListener() {
            final int depth = 10;
            int countdown = depth;
            public void actionPerformed(ActionEvent e) {
                countdown--;
                if (countdown <= 0) {
                    timer.stop();
                    component.repaint();
                } else {
                    int width = component.getWidth();
                    int stepX = width / depth * (depth - countdown) / 2;
                    int height = component.getHeight();
                    int stepY = height / depth * (depth - countdown) / 2;
                    Graphics g = component.getGraphics();
                    g.setColor(Color.BLACK);
                    g.drawRect(stepX, stepY, width - stepX * 2, height - stepY * 2);
                }
            }

        });
        timer.start();
    }

    public static void surroundComponent(final JComponent component) {
        final Timer timer = new javax.swing.Timer(30, null);
        timer.addActionListener(new ActionListener() {
            final JFrame frame = MorenoApp.getApplication().getMainFrame();
            int depth = 0;
            int[] pathx;
            int[] pathy;
            float pathLength;
            {
                Point[] path = new Point[6];
                Point pointMouse = MouseInfo.getPointerInfo().getLocation();
                path[0] = pointMouse;
                Point pointComponentTopLeft = component.getLocationOnScreen();
                Point pointFrame = frame.getLocationOnScreen();
                pointComponentTopLeft.translate(-pointFrame.x, -pointFrame.y);
                path[1] = pointComponentTopLeft;
                Point pointComponentTopRight = new Point(pointComponentTopLeft);
                pointComponentTopRight.translate(component.getWidth(), 0);
                path[2] = pointComponentTopRight;
                Point pointComponentBottomRight = new Point(pointComponentTopRight);
                pointComponentBottomRight.translate(0, component.getHeight());
                path[3] = pointComponentBottomRight;
                Point pointComponentBottomLeft = new Point(pointComponentTopLeft);
                pointComponentBottomLeft.translate(0, component.getHeight());
                path[4] = pointComponentBottomLeft;
                path[5] = pointComponentTopLeft;

                pathLength = 0;
                Point prev = null;
                for (Point p : path) {
                    if (prev != null) {
                        pathLength += p.distance(prev.x, prev.y);
                    }
                    prev = p;
                }
                pathx = new int[path.length];
                pathy = new int[path.length];
                for (int i = 0; i < path.length; i++) {
                    pathx[i] = path[i].x;
                    pathy[i] = path[i].y;
                }
            }

            public void actionPerformed(ActionEvent e) {
                depth++;
                if (depth > 125 || component.isShowing() == false) {
                    timer.stop();
                    frame.repaint();
                } else if (depth <= 100) {
                    Graphics2D g = (Graphics2D) frame.getGraphics();


                    float[] dash;
                    {
                        List<Float> dashList = new ArrayList<Float>();
                        float progress = depth / 100.0f;
                        float filled = 0;
                        float dashLen = 15.0f;
                        float spaceLen = 7.0f;
                        while (filled < pathLength) {
                            dashList.add(dashLen);
                            if ((filled / pathLength) >= progress) {
                                dashList.add(pathLength - filled);
                                dashList.add(pathLength);
                                break;
                            } else {
                                dashList.add(spaceLen);
                            }
                            filled += dashLen + spaceLen;
                        }
                        dash = new float[dashList.size()];
                        for (int i = 0; i < dash.length; i++) {
                            dash[i] = dashList.get(i);
                        }
                    }
                    g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1, dash, 0.0f));

                    g.setColor(Color.BLACK);
                    g.drawPolyline(pathx, pathy, pathx.length);
                }
            }

        });
        timer.start();
    }


    public static void debugShowComponentInFrame(JComponent component) {
        JFrame frame = new JFrame("Debugging panel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(component);
        frame.pack();
        frame.setVisible(true);
    }

}
